home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / python-support / gnome-games-data / glchess / gtkui / gtkui.py < prev    next >
Encoding:
Python Source  |  2009-04-14  |  31.7 KB  |  946 lines

  1. # -*- coding: utf-8 -*-
  2. # -*- coding: utf-8 -*-
  3.  
  4. __author__ = 'Robert Ancell <bob27@users.sourceforge.net>'
  5. __license__ = 'GNU General Public License Version 2'
  6. __copyright__ = 'Copyright 2005-2006  Robert Ancell'
  7.  
  8. __all__ = ['GtkUI']
  9.  
  10. # TODO: Extend base UI classes?
  11.  
  12. import os
  13. import sys
  14. import time
  15. from gettext import gettext as _
  16.  
  17. import gobject
  18. import gtk
  19. import gtk.glade
  20. import gtk.gdk
  21. import cairo
  22. import pango
  23.  
  24. from glchess.defaults import *
  25. import LaunchpadIntegration
  26.  
  27. # Stop PyGTK from catching exceptions
  28. os.environ['PYGTK_FATAL_EXCEPTIONS'] = '1'
  29.  
  30. import glchess.config
  31. import glchess.ui
  32. import glchess.chess.board
  33. import dialogs
  34. import log
  35. import chessview
  36. import network
  37.  
  38. # Mark all windows with our icon
  39. gtk.window_set_default_icon_name(ICON_NAME)
  40.  
  41. def loadGladeFile(name, root = None):
  42.     return gtk.glade.XML(os.path.join(GLADE_DIR, name), root, domain = DOMAIN)
  43.  
  44. class GLibTimer(glchess.ui.Timer):
  45.     """
  46.     """
  47.     
  48.     # FIXME: time.time() is _not_ monotonic so this is not really safe at all...
  49.  
  50.     def __init__(self, ui, feedback, duration):
  51.         self.ui        = ui
  52.         self.feedback  = feedback
  53.         self.tickTime  = 0
  54.         self.reportedTickTime = 0
  55.         
  56.         self.timer     = None
  57.         self.tickTimer = None
  58.         self.startTime = None
  59.         self.set(duration * 1000)
  60.         
  61.     def set(self, duration):
  62.         """
  63.         """
  64.         if self.timer is not None:
  65.             gobject.source_remove(self.timer)
  66.         if self.tickTimer is not None:
  67.             gobject.source_remove(self.tickTimer)
  68.         self.duration = duration
  69.         self.consumed = 0
  70.         
  71.         # Notified if the second has changed      
  72.         
  73.     def __consumed(self, now):
  74.         # Total time - time at last start - time since start
  75.         if self.startTime is None:
  76.             return self.consumed
  77.         else:
  78.             return self.consumed + (now - self.startTime)
  79.         
  80.     def getRemaining(self):
  81.         """Extends ui.Timer"""
  82.         return self.duration - self.__consumed(int(1000 * time.time()))
  83.  
  84.     def pause(self):
  85.         """Extends ui.Timer"""
  86.         if self.timer is None:
  87.             return
  88.         
  89.         # Calculate the amount of time to use when restarted
  90.         self.consumed = self.__consumed(int(1000 * time.time()))
  91.         
  92.         # Remove timers
  93.         gobject.source_remove(self.timer)
  94.         if self.tickTimer is not None:
  95.             gobject.source_remove(self.tickTimer)
  96.         self.timer = None
  97.         self.tickTimer = None
  98.     
  99.     def run(self):
  100.         """Extends ui.Timer"""
  101.         if self.timer is not None:
  102.             return
  103.         
  104.         # Notify when all time runs out
  105.         self.startTime = int(1000 * time.time())
  106.         self.timer = gobject.timeout_add(self.duration - self.consumed, self.__expired)
  107.         
  108.         # Notify on the next second boundary
  109.         self.__setSecondTimer(self.startTime)
  110.  
  111.     def __setSecondTimer(self, now):
  112.         """Set a timer to expire on the next second boundary"""
  113.         assert(self.tickTimer is None)
  114.         
  115.         # Round the remaining time up to the nearest second
  116.         consumed = self.__consumed(now)
  117.         t = 1000 * (consumed / 1000 + 1)
  118.         if t <= self.reportedTickTime:
  119.             self.tickTime = self.reportedTickTime + 1000
  120.         else:
  121.             self.tickTime = t
  122.         
  123.         # Notify on this time
  124.         if self.tickTime > self.duration:
  125.             self.tickTimer = None
  126.         else:
  127.             self.tickTimer = gobject.timeout_add(self.tickTime - consumed, self.__tick)
  128.  
  129.     def __expired(self):
  130.         """Called by GLib main loop"""
  131.         self.feedback.onTick(0)
  132.         self.feedback.onExpired()
  133.         if self.tickTimer is not None:
  134.             gobject.source_remove(self.tickTimer)
  135.         self.timer = None
  136.         self.tickTimer = None
  137.         return False
  138.  
  139.     def __tick(self):
  140.         """Called by GLib main loop"""
  141.         self.reportedTickTime = self.tickTime
  142.         self.feedback.onTick((self.duration - self.tickTime) / 1000)
  143.         self.tickTimer = None
  144.         self.__setSecondTimer(int(1000 * time.time()))
  145.         return False
  146.  
  147.     def delete(self):
  148.         """Extends ui.Timer"""
  149.         gobject.source_remove(self.timer)
  150.  
  151. class GtkUI(glchess.ui.UI):
  152.     """
  153.     """
  154.     # The Gtk+ GUI
  155.     _gui               = None
  156.     
  157.     # The time stored for animation
  158.     __lastTime         = None
  159.     __animationTimer   = None
  160.     
  161.     # The Gtk+ list model of the available player types
  162.     __playerModel      = None
  163.  
  164.     # The about dialog open
  165.     __aboutDialog      = None
  166.  
  167.     # Dictionary of save game dialogs keyed by view
  168.     __saveGameDialogs  = None
  169.  
  170.     __renderGL         = False
  171.     openGLInfoPrinted  = False
  172.  
  173.     # TODO
  174.     __joinGameDialogs  = None
  175.     __networkGames     = None
  176.     
  177.     __defaultWhiteAI   = None
  178.     __defaultBlackAI   = None
  179.  
  180.     __attentionCounter = 0
  181.  
  182.     whiteTimeString    = '‚àû'
  183.     blackTimeString    = '‚àû'
  184.     
  185.     # The window width and height when unmaximised and not fullscreen
  186.     width              = None
  187.     height             = None
  188.     isFullscreen       = False
  189.     isMaximised        = False
  190.     
  191.     view               = None
  192.  
  193.     def __init__(self, feedback):
  194.         """Constructor for a GTK+ glChess GUI"""
  195.         self.feedback = feedback
  196.         self._watches = {}
  197.         self.__networkGames = {}
  198.         self.newGameDialog = None
  199.         self.loadGameDialog = None
  200.         self.__saveGameDialogs = {}
  201.         self.__joinGameDialogs = []
  202.         
  203.         # Set the message panel to the tooltip style
  204.         # (copied from Gedit)
  205.         # In Gtk+ 2.11+ (I think) tip_window is now private so skip if it's not there (bug #459740)
  206.         tooltip = gtk.Tooltips()
  207.         tooltip.force_window()
  208.         if hasattr(tooltip, 'tip_window') and tooltip.tip_window != None:
  209.             tooltip.tip_window.ensure_style()
  210.             self._tooltipStyle = tooltip.tip_window.get_style()
  211.         else:
  212.             self._tooltipStyle = None
  213.         self._tooltipWidgetsDrawn = {}
  214.         
  215.         self._gui = loadGladeFile('glchess.glade')
  216.         self._gui.signal_autoconnect(self)
  217.         
  218.         self.mainWindow = self._gui.get_widget('glchess_app')
  219.         
  220.         # Workaround as Glade 2 always overrides the system style for toolbars
  221.         self.__getWidget('toolbar').unset_style()
  222.         
  223.         # Create the model for the player types
  224.         self.__playerModel = gtk.ListStore(str, str, str)
  225.         iter = self.__playerModel.append()
  226.         # Translators: Player Type Combo: Player is human controlled
  227.         self.__playerModel.set(iter, 0, '', 1, 'stock_person', 2, _('Human'))
  228.         
  229.         self.__logWindow = log.LogWindow(self._gui.get_widget('log_notebook'))
  230.         
  231.         # Make preferences dialog
  232.         self.preferences = dialogs.GtkPreferencesDialog(self)
  233.  
  234.         # Balance space on each side of the history combo
  235.         group = gtk.SizeGroup(gtk.SIZE_GROUP_BOTH)
  236.         group.add_widget(self.__getWidget('left_nav_box'))
  237.         group.add_widget(self.__getWidget('right_nav_box'))
  238.  
  239.         # History combo displays text data
  240.         combo = self.__getWidget('history_combo')
  241.         cell = gtk.CellRendererText()
  242.         combo.pack_start(cell, False)
  243.         combo.add_attribute(cell, 'text', 2)
  244.  
  245.         # Add launchpad integration
  246.         widget = self._gui.get_widget('help2_menu')
  247.         LaunchpadIntegration.set_sourcepackagename('gnome-games')
  248.         LaunchpadIntegration.add_items(widget,1,True,False)    
  249.         
  250.         self._updateViewButtons()
  251.         
  252.         # Watch for config changes
  253.         for key in ['show_toolbar', 'show_history', 'fullscreen',
  254.                     'show_3d', 'show_3d_smooth', 'show_comments', 'show_numbering',
  255.                     'show_move_hints',
  256.                     'width', 'height',
  257.                     'move_format', 'promotion_type', 'board_view',
  258.                     'enable_networking']:
  259.             glchess.config.watch(key, self.__applyConfig)
  260.  
  261.     # Public methods
  262.     
  263.     def setTooltipStyle(self, widget):
  264.         """Set a widget to be in the tooltip style.
  265.         
  266.         'widget' is the widget to modify.
  267.         """
  268.         if self._tooltipStyle is None:
  269.             return
  270.         widget.set_style(self._tooltipStyle)
  271.         widget.connect("expose_event", self._on_tooltip_expose_event)
  272.         widget.queue_draw()
  273.  
  274.     def _on_tooltip_expose_event(self, widget, event):
  275.         """Gtk+ callback"""
  276.         allocation = widget.allocation
  277.         widget.style.paint_flat_box(widget.window, gtk.STATE_NORMAL, gtk.SHADOW_OUT, None, widget, "tooltip",
  278.                                     allocation.x, allocation.y, allocation.width, allocation.height)
  279.                                     
  280.         # The first draw is corrupt for me so draw it twice.
  281.         # Bonus points to anyone who tracks down the problem and fixes it
  282.         if not self._tooltipWidgetsDrawn.has_key(widget):
  283.             self._tooltipWidgetsDrawn[widget] = True
  284.             widget.queue_draw()
  285.     
  286.     def watchFileDescriptor(self, fd):
  287.         """Extends ui.UI"""
  288.         self._watches[fd] = gobject.io_add_watch(fd, gobject.IO_IN | gobject.IO_PRI | gobject.IO_HUP | gobject.IO_ERR, self.__readData)
  289.         
  290.     def unwatchFileDescriptor(self, fd):
  291.         """Extends ui.UI"""
  292.         gobject.source_remove(self._watches.pop(fd))
  293.         
  294.     def writeFileDescriptor(self, fd):
  295.         """Extends ui.UI"""
  296.         gobject.io_add_watch(fd, gobject.IO_OUT, self.__writeData)
  297.         
  298.     def addTimer(self, feedback, duration):
  299.         """Extends ui.UI"""
  300.         return GLibTimer(self, feedback, duration)
  301.  
  302.     def __timerExpired(self, method):
  303.         method()
  304.         return True
  305.  
  306.     def __readData(self, fd, condition):
  307.         #print (fd, condition)
  308.         return self.feedback.onReadFileDescriptor(fd)
  309.  
  310.     def __writeData(self, fd, condition):
  311.         #print (fd, condition)
  312.         return self.feedback.onWriteFileDescriptor(fd)
  313.  
  314.     def addAIEngine(self, name):
  315.         """Register an AI engine.
  316.         
  317.         'name' is the name of the engine.
  318.         TODO: difficulty etc etc
  319.         """
  320.         iter = self.__playerModel.append()
  321.         self.__playerModel.set(iter, 0, name, 1, 'stock_notebook', 2, name)
  322.         
  323.         # Get the human to play against this AI
  324.         if self.__defaultBlackAI is None:
  325.             self.__defaultBlackAI = name
  326.  
  327.     def setView(self, title, feedback, isPlayable = True):
  328.         """Extends ui.UI"""
  329.         moveFormat = glchess.config.get('move_format')
  330.         showComments = glchess.config.get('show_comments')
  331.         self.view = chessview.GtkView(self, feedback, moveFormat = moveFormat, showComments = showComments)
  332.         self.view.setTitle(title)
  333.         self.view.isPlayable = isPlayable
  334.         self.view.viewWidget.setRenderGL(self.__renderGL)
  335.         viewport = self.__getWidget('game_viewport')
  336.         child = viewport.get_child()
  337.         if child is not None:
  338.             viewport.remove(child)
  339.         viewport.add(self.view.widget)
  340.  
  341.         # Set toolbar/menu buttons to state for this game
  342.         self._updateViewButtons()
  343.         
  344.         # Update timers
  345.         if self.view is not None:
  346.             self.setTimers(self.view.whiteTime, self.view.blackTime)
  347.  
  348.         return self.view
  349.  
  350.     def updateTitle(self):
  351.         """
  352.         """
  353.         # Set the window title to the name of the game
  354.         if self.view is not None and len(self.view.title) > 0:
  355.             if self.view.needsSaving:
  356.                 # Translators: Window title when playing a game that needs saving
  357.                 title = _('Chess - *%(game_name)s') % {'game_name': self.view.title}
  358.             else:
  359.                 # Translators: Window title when playing a game that is saved
  360.                 title = _('Chess - %(game_name)s') % {'game_name': self.view.title}
  361.         else:
  362.             # Translators: Window title when not playing a game
  363.             title = _('Chess')            
  364.         self.mainWindow.set_title(title)
  365.  
  366.     def addLogWindow(self, title, executable, description):
  367.         """
  368.         """
  369.         return self.__logWindow.addView(title, executable, description)
  370.     
  371.     def setTimers(self, whiteTime, blackTime):
  372.         """
  373.         """
  374.         # Translators: Game Timer Label: Indicates that game has no time limit
  375.         unlimitedTimeText = _('‚àû')
  376.         
  377.         if whiteTime is None:
  378.             whiteString = unlimitedTimeText
  379.         else:
  380.             t = whiteTime[1]
  381.             whiteString = '%i:%02i' % (t / 60, t % 60)
  382.         if blackTime is None:
  383.             blackString = unlimitedTimeText
  384.         else:
  385.             t = blackTime[1]
  386.             blackString = '%i:%02i' % (t / 60, t % 60)
  387.             
  388.         if whiteString != self.whiteTimeString:
  389.             self.whiteTimeString = whiteString
  390.             self._gui.get_widget('white_time_label').queue_draw()
  391.         if blackString != self.blackTimeString:
  392.             self.blackTimeString = blackString
  393.             self._gui.get_widget('black_time_label').queue_draw()
  394.  
  395.     def run(self):
  396.         """Run the UI.
  397.         
  398.         This method will not return.
  399.         """        
  400.         # Load configuration
  401.         for name in ['show_toolbar', 'show_history', 'show_3d', 'show_3d_smooth',
  402.                      'show_comments', 'show_numbering', 'show_move_hints',
  403.                      'move_format', 'promotion_type', 'board_view', 'maximised',
  404.                      'enable_networking']:
  405.             try:
  406.                 value = glchess.config.get(name)
  407.             except glchess.config.Error:
  408.                 pass
  409.             else:
  410.                 self.__applyConfig(name, value)
  411.         self.__resize()
  412.         
  413.         self.mainWindow.show()
  414.  
  415.         # Apply the fullscreen flag after the window has been shown otherwise
  416.         # gtk.Window.unfullscreen() stops working if the window is set to fullscreen
  417.         # before being shown. I haven't been able to reproduce this in the simple
  418.         # case (GTK+ 2.10.6-0ubuntu3).
  419.         self.__applyConfig('fullscreen', glchess.config.get('fullscreen'))
  420.         
  421.         gtk.main()
  422.         
  423.     # Extended methods
  424.  
  425.     def reportGameLoaded(self, game):
  426.         """Extends glchess.ui.UI"""
  427.         dialogs.GtkNewGameDialog(self, self.__playerModel, game)
  428.  
  429.     def addNetworkDialog(self, feedback):
  430.         """Extends glchess.ui.UI"""
  431.         self.__networkDialog = network.GtkNetworkGameDialog(self, feedback)
  432.         return self.__networkDialog
  433.                                  
  434.     def addNetworkGame(self, name, game):
  435.         """Extends glchess.ui.UI"""
  436.         self.__networkDialog.addNetworkGame(name, game)
  437.  
  438.     def removeNetworkGame(self, game):
  439.         """Extends glchess.ui.UI"""
  440.         self.__networkDialog.removeNetworkGame(game)
  441.             
  442.     def requestSave(self, title):
  443.         """Extends glchess.ui.UI"""
  444.         dialog = gtk.MessageDialog(flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
  445.                                    type = gtk.MESSAGE_WARNING,
  446.                                    message_format = title)
  447.         # Translators: Save Game Dialog: Notice that game needs saving
  448.         dialog.format_secondary_text(_("If you don't save the changes to this game will be permanently lost"))
  449.         # Translators: Save Game Dialog: Discard game button
  450.         dialog.add_button(_('Close _without saving'), gtk.RESPONSE_OK)
  451.         dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT)
  452.         dialog.add_button(gtk.STOCK_SAVE, gtk.RESPONSE_ACCEPT)
  453.  
  454.         response = dialog.run()
  455.         dialog.destroy()
  456.         if response == gtk.RESPONSE_ACCEPT:
  457.             return glchess.ui.SAVE_YES
  458.         elif response == gtk.RESPONSE_OK:
  459.             return glchess.ui.SAVE_NO
  460.         else:
  461.             return glchess.ui.SAVE_ABORT
  462.  
  463.     def close(self):
  464.         """Extends glchess.ui.UI"""
  465.         # Save the window size
  466.         if self.width is not None:
  467.             glchess.config.set('width', self.width)
  468.         if self.height is not None:
  469.             glchess.config.set('height', self.height)
  470.  
  471.     # Protected methods
  472.     
  473.     def _incAttentionCounter(self, offset):
  474.         """
  475.         """
  476.         self.__attentionCounter += offset
  477.         self.__updateAttention()
  478.         
  479.     def __updateAttention(self):
  480.         """
  481.         """
  482.         widget = self.mainWindow
  483.         widget.set_urgency_hint(self.__attentionCounter != 0 and not widget.is_active())
  484.         
  485.     def _on_focus_changed(self, widget, event):
  486.         """Gtk+ callback"""
  487.         self.__updateAttention()
  488.  
  489.     def _saveView(self, view, path):
  490.         """
  491.         """
  492.         if path is None:
  493.             error = None
  494.         else:
  495.             error = view.feedback.save(path)
  496.  
  497.         if error is not None:
  498.             return error        
  499.         self.__saveGameDialogs.pop(view)
  500.  
  501.     # Private methods
  502.  
  503.     def __resize(self):
  504.         try:
  505.             width = glchess.config.get('width')
  506.             height = glchess.config.get('height')
  507.         except glchess.config.Error:
  508.             return
  509.         
  510.         self.mainWindow.resize(width, height)
  511.  
  512.     def __applyConfig(self, name, value):
  513.         """
  514.         """
  515.         if name == 'width' or name == 'height':
  516.             self.__resize()
  517.             return
  518.  
  519.         # Show/hide the toolbar
  520.         if name == 'show_toolbar':
  521.             toolbar = self.__getWidget('toolbar')
  522.             if value is True:
  523.                 toolbar.show()
  524.             else:
  525.                 toolbar.hide()
  526.                 
  527.         elif name == 'enable_networking':
  528.             menuItem = self.__getWidget('menu_play_online_item')
  529.             toolbarButton = self.__getWidget('play_online_button')
  530.             if value is True:
  531.                 menuItem.show()
  532.                 toolbarButton.show()
  533.             else:
  534.                 menuItem.hide()
  535.                 toolbarButton.hide()
  536.             
  537.         # Show/hide the history
  538.         elif name == 'show_history':
  539.             box = self.__getWidget('navigation_box')
  540.             if value is True:
  541.                 box.show()
  542.             else:
  543.                 box.hide()
  544.                 
  545.         # Maximised mode
  546.         elif name == 'maximised':
  547.             window = self.mainWindow
  548.             if value is True:
  549.                 window.maximize()
  550.             else:
  551.                 window.unmaximize()
  552.  
  553.         # Fullscreen mode
  554.         elif name == 'fullscreen':
  555.             window = self.mainWindow
  556.             if value is True:
  557.                 window.fullscreen()
  558.             else:
  559.                 window.unfullscreen()
  560.  
  561.         # Enable/disable OpenGL rendering
  562.         elif name == 'show_3d':            
  563.             if value and not chessview.haveGLSupport:
  564.                 # Translators: No 3D Dialog: Title
  565.                 title = _('Unable to enable 3D mode')
  566.                 errors = '\n'.join(chessview.openGLErrors)
  567.                 # Translators: No 3D Dialog: Notification to user that they do not have libraries required to enable 3D.
  568.                 # %(error)s will be replaced with a list of reasons why 3D is not available.
  569.                 description = _("""You are unable to play in 3D mode due to the following problems:
  570. %(errors)s
  571.  
  572. Please contact your system administrator to resolve these problems, until then you will be able to play chess in 2D mode.""") % {'errors': errors}
  573.                 dialog = gtk.MessageDialog(type = gtk.MESSAGE_WARNING, message_format = title)
  574.                 dialog.format_secondary_text(description)
  575.                 dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_CLOSE)
  576.                 dialog.run()
  577.                 dialog.destroy()
  578.                 glchess.config.set('show_3d', False)
  579.                 value = False
  580.             self.__renderGL = value
  581.             self.__getWidget('menu_view_3d').set_active(value)
  582.             self.view.viewWidget.setRenderGL(value)
  583.  
  584.         elif name == 'show_3d_smooth':
  585.             if not chessview.haveGLAccumSupport:
  586.                 value = False
  587.             self.view.feedback.showSmooth(value)
  588.                 
  589.         elif name == 'show_comments':
  590.             self.view.setShowComments(value)
  591.  
  592.         elif name == 'show_move_hints':
  593.             self.view.feedback.showMoveHints(value)
  594.  
  595.         elif name == 'show_numbering':
  596.             self.view.feedback.showBoardNumbering(value)
  597.  
  598.         elif name == 'move_format':
  599.             self.view.setMoveFormat(value)
  600.                 
  601.         elif name == 'promotion_type':
  602.             pass
  603.     
  604.         elif name == 'board_view':
  605.             pass
  606.  
  607.         else:
  608.             assert(False), 'Unknown config item: %s' % name
  609.  
  610.     def startAnimation(self):
  611.         """Start the animation callback"""
  612.         if self.__animationTimer is None:
  613.             self.__lastTime = time.time()
  614.             self.__animationTimer = gobject.timeout_add(10, self.__animate)
  615.  
  616.     def __animate(self):
  617.         # Get the timestep, if it is less than zero or more than a second
  618.         # then the system clock was probably changed.
  619.         now = time.time()
  620.         step = now - self.__lastTime
  621.         if step < 0.0:
  622.             step = 0.0
  623.         elif step > 1.0:
  624.             step = 1.0
  625.         self.__lastTime = now
  626.         
  627.         # Animate!
  628.         animating = self.feedback.onAnimate(step)
  629.         if not animating:
  630.             self.__animationTimer = None
  631.         
  632.         # Keep/delete timer
  633.         return animating
  634.  
  635.     def __getWidget(self, name):
  636.         widget = self._gui.get_widget(name)
  637.         assert(widget is not None), 'Unable to find widget: %s' % name
  638.         return widget
  639.  
  640.     def _on_white_time_paint(self, widget, event):
  641.         """Gtk+ callback"""
  642.         self.__drawTime(self.whiteTimeString, widget, (0.0, 0.0, 0.0), (1.0, 1.0, 1.0))
  643.  
  644.     def _on_black_time_paint(self, widget, event):
  645.         """Gtk+ callback"""
  646.         self.__drawTime(self.blackTimeString, widget, (1.0, 1.0, 1.0), (0.0, 0.0, 0.0))
  647.  
  648.     def __drawTime(self, text, widget, fg, bg):
  649.         """
  650.         """
  651.         if widget.state == gtk.STATE_INSENSITIVE:
  652.             alpha = 0.5
  653.         else:
  654.             alpha = 1.0
  655.         context = widget.window.cairo_create()
  656.         context.set_source_rgba(bg[0], bg[1], bg[2], alpha)
  657.         context.paint()
  658.         
  659.         (_, _, w, h) = widget.get_allocation()
  660.         
  661.         context.set_source_rgba(fg[0], fg[1], fg[2], alpha)
  662.         context.select_font_face('fixed', cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_BOLD)
  663.         context.set_font_size(0.6 * h)
  664.         (x_bearing, y_bearing, width, height, _, _) = context.text_extents(text)
  665.         context.move_to((w - width) / 2 - x_bearing, (h - height) / 2 - y_bearing)
  666.         context.show_text(text)
  667.         
  668.         # Resize to fit text
  669.         widget.set_size_request(int(width) + 6, -1)
  670.  
  671.     def _on_toggle_3d_clicked(self, widget):
  672.         """Gtk+ callback"""
  673.         if widget.get_active():
  674.             value = True
  675.         else:
  676.             value = False
  677.         glchess.config.set('show_3d', value)
  678.  
  679.     def _on_show_logs_clicked(self, widget):
  680.         """Gtk+ callback"""
  681.         window = self._gui.get_widget('log_window')
  682.         if widget.get_active():
  683.             window.present()
  684.         else:
  685.             window.hide()
  686.  
  687.     def _on_history_combo_changed(self, widget):
  688.         """Gtk+ callback"""
  689.         model = widget.get_model()
  690.         iter = widget.get_active_iter()
  691.         if iter is None:
  692.             return
  693.         
  694.         # Get the move number
  695.         moveNumber = model.get_value(iter, 1)
  696.         
  697.         if moveNumber == len(model) - 1:
  698.             moveNumber = -1
  699.  
  700.         # Disable buttons when at the end
  701.         haveMoves = len(model) > 1
  702.         for widget in ('first_move_button', 'prev_move_button'):
  703.             self.__getWidget(widget).set_sensitive(haveMoves and moveNumber != 0)
  704.         for widget in ('last_move_button', 'next_move_button'):
  705.             self.__getWidget(widget).set_sensitive(haveMoves and moveNumber != -1)
  706.  
  707.         self.view._setMoveNumber(moveNumber)
  708.  
  709.     def __selectMoveNumber(self, moveNumber):
  710.         """FIXME
  711.         """
  712.         combo = self.__getWidget('history_combo')
  713.         
  714.         # Limit moves to the maximum value
  715.         maxNumber = len(combo.get_model())
  716.  
  717.         # Allow negative indexing
  718.         if moveNumber < 0:
  719.             moveNumber = maxNumber + moveNumber
  720.         if moveNumber < 0:
  721.             moveNumber = 0
  722.         if moveNumber >= maxNumber:
  723.             moveNumber = maxNumber - 1
  724.         
  725.         combo.set_active(moveNumber)
  726.  
  727.     def __selectMoveNumberRelative(self, offset):
  728.         """FIXME
  729.         """
  730.         combo = self.__getWidget('history_combo')
  731.         selected = combo.get_active()
  732.         maxNumber = len(combo.get_model())
  733.         new = selected + offset
  734.         if new < 0:
  735.             new = 0
  736.         elif new >= maxNumber:
  737.             new = maxNumber - 1
  738.         self.__selectMoveNumber(new)
  739.  
  740.     def _on_history_start_clicked(self, widget):
  741.         """Gtk+ callback"""
  742.         self.__selectMoveNumber(0)
  743.  
  744.     def _on_history_previous_clicked(self, widget):
  745.         """Gtk+ callback"""
  746.         self.__selectMoveNumberRelative(-1)
  747.  
  748.     def _on_history_next_clicked(self, widget):
  749.         """Gtk+ callback"""
  750.         self.__selectMoveNumberRelative(1)
  751.  
  752.     def _on_history_latest_clicked(self, widget):
  753.         """Gtk+ callback"""
  754.         self.__selectMoveNumber(-1)
  755.  
  756.     def _updateViewButtons(self):
  757.         """
  758.         """
  759.         enable = self.view is not None and self.view.isPlayable
  760.         for widget in ('save_game_button', 'menu_save_item', 'menu_save_as_item'):
  761.             self.__getWidget(widget).set_sensitive(enable)
  762.  
  763.         combo = self.__getWidget('history_combo')
  764.         if self.view is None:
  765.             if combo.get_model() != None:
  766.                 combo.set_model(None)
  767.         else:
  768.             (model, selected) = self.view._getModel()
  769.             combo.set_model(model)
  770.             if selected < 0:
  771.                 selected = len(model) + selected
  772.             combo.set_active(selected)
  773.         self.__getWidget('navigation_box').set_sensitive(enable)
  774.         
  775.         enable = enable and self.view.gameResult is None
  776.         '''FIXME! for widget in ('menu_resign', 'resign_button', 'menu_claim_draw'):
  777.             self.__getWidget(widget).set_sensitive(enable)'''
  778.  
  779.     def _on_new_game_button_clicked(self, widget):
  780.         """Gtk+ callback"""
  781.         if self.newGameDialog:
  782.             self.newGameDialog.window.present()
  783.         else:
  784.             self.newGameDialog = dialogs.GtkNewGameDialog(self, self.__playerModel)
  785.  
  786.     def _on_join_game_button_clicked(self, widget):
  787.         """Gtk+ callback"""
  788.         self.feedback.onNewNetworkGame()
  789.  
  790.     def _on_open_game_button_clicked(self, widget):
  791.         """Gtk+ callback"""
  792.         if self.loadGameDialog:
  793.             self.loadGameDialog.window.present()
  794.         else:
  795.             self.loadGameDialog = dialogs.GtkLoadGameDialog(self)
  796.         
  797.     def _on_save_game_button_clicked(self, widget):
  798.         """Gtk+ callback"""
  799.         if self.view.feedback.getFileName() is not None:
  800.             self.view.feedback.save()
  801.             return
  802.         
  803.         try:
  804.             dialog = self.__saveGameDialogs[self.view]
  805.         except KeyError:
  806.             dialog = self.__saveGameDialogs[self.view] = dialogs.GtkSaveGameDialog(self, self.view)
  807.         dialog.window.present()
  808.  
  809.     def _on_save_as_game_button_clicked(self, widget):
  810.         """Gtk+ callback"""
  811.         try:
  812.             dialog = self.__saveGameDialogs[self.view]
  813.         except KeyError:
  814.             dialog = self.__saveGameDialogs[self.view] = dialogs.GtkSaveGameDialog(self, self.view, self.view.feedback.getFileName())
  815.         dialog.window.present()
  816.         
  817.     def _on_undo_move_clicked(self, widget):
  818.         """Gtk+ callback"""
  819.         self.view.feedback.undo()
  820.  
  821.     def _on_resign_clicked(self, widget):
  822.         """Gtk+ callback"""
  823.         self.view.feedback.resign()
  824.  
  825.     def _on_claim_draw_clicked(self, widget):
  826.         """Gtk+ callback"""
  827.         if self.view.feedback.claimDraw():
  828.             return
  829.         
  830.         # Translators: Draw Dialog: Title
  831.         title = _("Unable to claim draw")
  832.         # Translators: Draw Dialog: Notify user why they cannot claim draw
  833.         message = _("""You may claim a draw when:
  834. a) The board has been in the same state three times (Three fold repetition)
  835. b) Fifty moves have occured where no pawn has moved and no piece has been captured (50 move rule)""")
  836.         
  837.         dialog = gtk.MessageDialog(flags = gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
  838.                                    type = gtk.MESSAGE_WARNING,
  839.                                    message_format = title)
  840.         dialog.format_secondary_text(message)
  841.         dialog.add_button(gtk.STOCK_CLOSE, gtk.RESPONSE_ACCEPT)
  842.         dialog.run()
  843.         dialog.destroy()
  844.  
  845.     def _on_preferences_clicked(self, widget):
  846.         """Gtk+ callback"""
  847.         self.preferences.setVisible(True)
  848.  
  849.     def _on_help_clicked(self, widget):
  850.         """Gtk+ callback"""
  851.         try:
  852.             gtk.show_uri(self.mainWindow.get_screen(), "ghelp:glchess", gtk.get_current_event_time())
  853.         except gobject.GError, e:
  854.             # TODO: This should be a pop-up dialog
  855.             print _('Unable to display help: %s') % str(e)
  856.  
  857.     def _on_view_fullscreen_clicked(self, widget):
  858.         """Gtk+ callback"""
  859.         glchess.config.set('fullscreen', True)
  860.         
  861.     def _on_view_unfullscreen_clicked(self, widget):
  862.         """Gtk+ callback"""
  863.         glchess.config.set('fullscreen', False)
  864.         
  865.     def _on_3d_support_dialog_delete_event(self, widget, event):
  866.         """Gtk+ callback"""
  867.         # Stop the dialog from deleting itself
  868.         return True
  869.  
  870.     def _on_3d_support_dialog_response(self, widget, responseId):
  871.         """Gtk+ callback"""
  872.         if self.__aboutDialog is not None:
  873.             return
  874.         widget.hide()
  875.         return False
  876.  
  877.     def _on_about_clicked(self, widget):
  878.         """Gtk+ callback"""
  879.         if self.__aboutDialog is not None:
  880.             return
  881.         
  882.         dialog = self.__aboutDialog = gtk.AboutDialog()
  883.         dialog.set_transient_for(self.mainWindow)
  884.         dialog.set_name(APPNAME)
  885.         dialog.set_version(VERSION)
  886.         dialog.set_copyright(COPYRIGHT)
  887.         dialog.set_license(LICENSE[0] + '\n\n' + LICENSE[1] + '\n\n' +LICENSE[2])
  888.         dialog.set_wrap_license(True)
  889.         dialog.set_comments(DESCRIPTION)
  890.         dialog.set_authors(AUTHORS)
  891.         dialog.set_artists(ARTISTS)
  892.         dialog.set_translator_credits(_("translator-credits"))
  893.         dialog.set_website(WEBSITE)
  894.         dialog.set_website_label(WEBSITE_LABEL)
  895.         dialog.set_logo_icon_name(ICON_NAME)
  896.         dialog.connect('response', self._on_glchess_about_dialog_close)
  897.         dialog.show()
  898.         
  899.     def _on_glchess_about_dialog_close(self, widget, event):
  900.         """Gtk+ callback"""
  901.         self.__aboutDialog.destroy()
  902.         self.__aboutDialog = None
  903.         return False
  904.         
  905.     def _on_log_window_delete_event(self, widget, event):
  906.         """Gtk+ callback"""
  907.         self._gui.get_widget('menu_view_logs').set_active(False)
  908.         
  909.         # Stop the event - the window will be closed by the menu event
  910.         return True
  911.     
  912.     def _on_resize(self, widget, event):
  913.         """Gtk+ callback"""
  914.         if self.isMaximised or self.isFullscreen:
  915.             return
  916.         self.width = event.width
  917.         self.height = event.height
  918.  
  919.     def _on_window_state_changed(self, widget, event):
  920.         """Gtk+ callback"""
  921.         if event.changed_mask & gtk.gdk.WINDOW_STATE_MAXIMIZED:
  922.             self.isMaximised = event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED != 0
  923.             glchess.config.set('maximised', self.isMaximised)
  924.             
  925.         if event.changed_mask & gtk.gdk.WINDOW_STATE_FULLSCREEN:
  926.             self.isFullscreen = event.new_window_state & gtk.gdk.WINDOW_STATE_FULLSCREEN != 0
  927.             if self.isFullscreen:
  928.                 self._gui.get_widget('menu_fullscreen').hide()
  929.                 self._gui.get_widget('menu_leave_fullscreen').show()
  930.             else:
  931.                 self._gui.get_widget('menu_leave_fullscreen').hide()
  932.                 self._gui.get_widget('menu_fullscreen').show()
  933.  
  934.     def _on_close_window(self, widget, event):
  935.         """Gtk+ callback"""
  936.         self.feedback.onQuit()
  937.         return True
  938.         
  939.     def _on_menu_quit(self, widget):
  940.         """Gtk+ callback"""
  941.         self.feedback.onQuit()
  942.  
  943. if __name__ == '__main__':
  944.     ui = GtkUI()
  945.     ui.run()
  946.